package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.ANGLE_360;
import static xj.graph2d.DrawShapeConstants.ANGLE_90;
import static xj.graph2d.DrawShapeConstants.DRAW_DEFAULT;
import static xj.graph2d.DrawShapeConstants.DRAW_GRAPH_OVER_HINT;
import static xj.graph2d.DrawShapeConstants.DRAW_HOTSPOT_HINT;
import static xj.graph2d.DrawShapeConstants.DRAW_INSERTION_HINT;
import static xj.graph2d.DrawShapeConstants.DRAW_OUTLINE_REAL;
import static xj.graph2d.DrawShapeConstants.DRAW_OVER_HINT;
import static xj.graph2d.DrawShapeConstants.DRAW_PRINT;
import static xj.graph2d.DrawShapeConstants.DRAW_SELECTED;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_NONE;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_SIZE;
import static xj.graph2d.DrawShapeConstants.NORMAL;
import static xj.graph2d.DrawShapeConstants.PointEastEdge;
import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointNECorner;
import static xj.graph2d.DrawShapeConstants.PointNWCorner;
import static xj.graph2d.DrawShapeConstants.PointNorthEdge;
import static xj.graph2d.DrawShapeConstants.PointOnMessage;
import static xj.graph2d.DrawShapeConstants.PointOnRotationHandle;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.PointSECorner;
import static xj.graph2d.DrawShapeConstants.PointSWCorner;
import static xj.graph2d.DrawShapeConstants.PointSouthEdge;
import static xj.graph2d.DrawShapeConstants.PointWestEdge;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TEMPLATE;
import static xj.graph2d.DrawShapeConstants.SCALE_DEFAULT;
import static xj.graph2d.DrawShapeConstants.SCALE_EXCLUDE_SUBSHAPE;
import static xj.graph2d.DrawShapeConstants.WRITE_XML_DEFAULT;
import static xj.graph2d.DrawShapeConstants.anchorColor;
import static xj.graph2d.DrawShapeConstants.cursorcolor;
import static xj.graph2d.DrawShapeConstants.cpady;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.debugFrameColor1;
import static xj.graph2d.DrawShapeConstants.debugFrameColor2;
import static xj.graph2d.DrawShapeConstants.defaultFrameColor;
import static xj.graph2d.DrawShapeConstants.frameMarkFilled;
import static xj.graph2d.DrawShapeConstants.framePad;
import static xj.graph2d.DrawShapeConstants.handleLength;
import static xj.graph2d.DrawShapeConstants.hotspotColor;
import static xj.graph2d.DrawShapeConstants.insertionHintColor;
import static xj.graph2d.DrawShapeConstants.markStroke;
import static xj.graph2d.DrawShapeConstants.overColor;
import static xj.graph2d.DrawShapeConstants.overColorGraphNode;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.SwingConstants;

import org.apache.batik.ext.awt.RadialGradientPaint;

import xj.graph2d.shapes.PolyShape;
import xj.graph2d.util.DrawingUtilities;
import xj.lang.CodeWriter;
import xj.meta.ModelElement;
import xj.meta.ModelRepository;
import xj.util.gui.Point2DFloat;
import xj.util.gui.Rectangle2DFloat;
import xj.util.xml.XMLUtil;

abstract public class DrawShapeBase 
  implements DrawShape {

  protected Point2D p1, p2;

  protected DrawAttr attr;

  protected Arrow head, tail;

  protected boolean filled = false;

  protected boolean shadow = false;

  protected int shadowWidth = 3;

  protected boolean blured = false;

  protected boolean fixedRatio = false;

  protected boolean editable = true;

  protected boolean allowRotation = true;

  protected boolean symetricResize = true;

  protected boolean visible = true;

  protected boolean movable = true;

  protected boolean sticky = false;

  protected boolean scalable = true;

  protected boolean resizable = true;

  protected boolean searchable = true;

  protected boolean selectable = true;

  protected boolean atomic = true;

  protected boolean detachable = true;

  protected boolean fitted = false;

  protected boolean needFrameRect = true;

  protected DrawShape parent = null;

  protected int scaleMode = SCALE_DEFAULT;

  protected String name;

  protected String roleName;

  protected String templateName;

  protected String displayName;

  protected String annotation;

  protected int role = -1;

  protected int flag = -1;

  protected int deco = -1;

  protected ModelElement model;

  protected ModelListener modelListener;

  protected Rectangle2D bounds, framebounds;

  protected int cursor = 0;

  protected boolean showcursor = false;

  protected boolean showborder = true;

  protected HashMap<String, Object> customAttr = null;

  // subshapes are in the same coordinate system as the owner shape
  protected List<DrawShape> subshapes = null;

  protected DrawShape selectedSubshape = null;

  protected boolean clipSubshapes = false;

  protected boolean cloneLabel = true;

  protected boolean acceptSubshape = false;

  protected float angle;

  protected AffineTransform at = null;

  protected AffineTransform inv = null;

  // transient attributes: not saved, not copied 

  protected DrawShapeMessage message; 

  protected int displayMode = NORMAL; 

  protected static int drawOutlineMode = DRAW_OUTLINE_REAL;

  protected static boolean cloneModel = true;

  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      s.p1.setLocation(p1);
      s.p2.setLocation(p2);
      s.bounds.setRect(bounds);
      s.framebounds.setRect(framebounds);
      if (head != null) {
	s.head = (Arrow) head.clone();
	s.head.setOwner(s);
      }
      if (tail != null) {
	s.tail = (Arrow) tail.clone();
	s.tail.setOwner(s);
      }
      s.filled = filled;
      s.shadow = shadow;
      s.shadowWidth = shadowWidth;
      s.blured = blured;
      s.fixedRatio = fixedRatio;

      s.name = name;
      s.roleName = roleName;
      s.templateName = templateName;
      s.displayName = displayName;
      s.annotation = annotation;
      s.role = role;
      s.flag = flag;
      s.deco = deco;

      s.detachable = detachable;
      s.editable = editable;
      s.allowRotation = allowRotation;
      s.symetricResize = symetricResize;
      s.visible = visible;
      s.movable = movable;
      s.sticky = sticky;
      s.scalable = scalable;
      s.scaleMode = scaleMode;
      s.resizable = resizable;
      s.searchable = searchable;
      s.selectable = selectable;
      s.atomic = atomic;
      s.fitted = fitted;
      s.needFrameRect = needFrameRect;
      if (attr != null) {
	s.attr = (DrawAttr) attr.clone();
      }
      s.cursor = cursor;
      s.showcursor = showcursor;
      s.showborder = showborder;

      s.cloneLabel = cloneLabel;
      s.clipSubshapes = clipSubshapes;
      s.acceptSubshape = acceptSubshape;

      if (customAttr != null) { 
	s.customAttr = (HashMap) customAttr.clone();
      }

      //cloneSubshapes(s);
    }
  }

  public void cloneModel(DrawShapeBase s) {
    if (s != null) {
      // clone model;
      if (model != null) {
	if (cloneModel) {
	  try {
	    s.model = (ModelElement) model.clone();
	  } catch (CloneNotSupportedException e) {
	    System.err.println("DrawShapeBase.copyTo(): clone model exception: " + e);
	    s.model = model;
	  }
	} else {
	  try {
	    Class<? extends ModelElement> c = model.getClass();
	    s.model = c.newInstance();
	  } catch (InstantiationException e) {
	    System.err.println("DrawShapeBase.copyTo(): new model exception: " + e);
	  } catch (IllegalAccessException e) {
	    System.err.println("DrawShapeBase.copyTo(): new model exception: " + e);
	  }
	}
	CloneManager.addShape(model.getId(), s);
      }
      if (modelListener != null) {
	try {
	  s.setModelListener((ModelListener) modelListener.clone());
	} catch (CloneNotSupportedException e) {
	  // System.out.println("DrawShapeBase.copyTo(): clone model listener exception: " + e);
	  s.modelListener = null;
	}
      }
    }
  }

  protected void cloneSubshapes(DrawShapeBase s, boolean deepClone) 
    throws CloneNotSupportedException {
    if (s != null) {
      if (subshapes != null) {
	if (s.subshapes == null) {
	  s.subshapes = new ArrayList<DrawShape>();
	}
	int count = subshapes.size();
	for (int i = 0; i < count; i++) {
	  DrawShape shape = subshapes.get(i);
	  if (shape != null) {
	    if (deepClone || 
		//shape instanceof Group || 
		!(shape instanceof GraphComponentShape)) {		

	      if (!isLabel(shape) || isCloneLabel()) {
		//DrawShape newShape = (DrawShape) shape.clone();
		DrawShape newShape = shape.cloneDrawShape(deepClone);
		// shape.calculateBounds();
		s.addShape(newShape);
	      }
	    }

	  }
	}
      }
      s.arrangeSubshapes();
    }
  }

  abstract public DrawShape makeInstance() 
    throws CloneNotSupportedException;

  public Object clone() 
    throws CloneNotSupportedException {
    return cloneDrawShape(true);
  }

  public DrawShape cloneDrawShape(boolean deepClone)
    throws CloneNotSupportedException {

    DrawShape s = makeInstance();
    if (s instanceof DrawShapeBase) {
      DrawShapeBase shape = (DrawShapeBase) s;
      copyTo(shape);
      cloneSubshapes(shape, deepClone);
      cloneModel(shape);
    }
    return s;   
  }

  public void normalize() {
    float xmin = (float) (Math.min(p1.getX(), p2.getX()));
    float xmax = (float) (Math.max(p1.getX(), p2.getX()));
    float ymin = (float) (Math.min(p1.getY(), p2.getY()));
    float ymax = (float) (Math.max(p1.getY(), p2.getY()));
    p1.setLocation(xmin, ymin);
    p2.setLocation(xmax, ymax);
  }

  /*
   * Managing sub-shapes
   */

  public List<DrawShape> getShapes() {
    return subshapes;
  }

  public int addShape(DrawShape s) {
    return addShape(s, false);
  }

  public int addShape(DrawShape s, boolean select) {
    if (s != null) {
      if (subshapes == null) {
	subshapes = new ArrayList<DrawShape>();
      }
      s.setParent(this);
      subshapes.add(s);
      if (select) {
	setSelectedSubShape(s);
      }
      return subshapes.size() - 1;
    }
    return -1;
  }

  public void addShapes(List<DrawShape> list) {
    if (list != null) {
      int n = list.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = list.get(i); 
	addShape(s);
      }
    }
  }

  public void insertShapeAt(DrawShape s, int i) {
    insertShapeAt(s, i, false);
  }

  public void insertShapeAt(DrawShape s, int i, boolean select) {
    if (s != null) {
      if (subshapes == null) {
	subshapes = new ArrayList<DrawShape>();
      }
      int n = subshapes.size();
      if (i < 0) {
	i = 0;
      }
      if (i > n) {
	i = n;
      }
      subshapes.add(i, s);
      s.setParent(this);
      if (s.getThisDrawAttr() == null && 
	  getDrawAttr() != null) {
	s.calculateBounds();
      }
      if (select) {
	setSelectedSubShape(s);
      }
    }
  }

  /**
   * Return the index of s
   */
  public int insertShapeAtTop(DrawShape s, boolean select) {
    if (s != null) {
      subshapes.add(s);
      s.setParent(this);
      if (s.getThisDrawAttr() == null && getDrawAttr() != null) {
	s.calculateBounds();
      }
      if (select) {
	setSelectedSubShape(s);
      }
      return subshapes.size() - 1;
    } else {
      return -1;
    }
  }

  public void insertShapeAtBottom(DrawShape s, boolean select) {
    insertShapeAt(s, 0, select);
  }

  public int indexOfShape(DrawShape s) {
    if (s != null && 
	subshapes != null) {
      return subshapes.indexOf(s);
    } else {
      return -1;
    }
  }

  public DrawShape removeShape(int i) {
    if (subshapes != null && 
	i >= 0 && 
	i < subshapes.size()) {
      DrawShape s = subshapes.get(i);
      s.setParent(null);
      subshapes.remove(i);
      return s;
    }
    return null;
  }

  public DrawShape getShape(int i) {
    if (subshapes != null && 
	i >= 0 && 
	i < subshapes.size()) {
      return subshapes.get(i);
    } else {
      return null;
    }
  }

  public boolean removeShape(DrawShape s) {
    if (s != null && 
	subshapes != null && 
	subshapes.contains(s)) {
      s.setParent(null);
      subshapes.remove(s);
      return true;
    }
    return false;
  }

  public void moveToFirst(DrawShape s) { 
    if (s != null && 
	subshapes != null) { 
      int i = subshapes.indexOf(s);
      if (i > 0) { 
	subshapes.remove(i);
	subshapes.add(0, s);
      }
    }
  }

  public void moveToLast(DrawShape s) { 
    if (s != null && 
	subshapes != null) { 
      int n = subshapes.size();
      int i = subshapes.indexOf(s);
      if (i > 0 && i < n - 1) { 
	subshapes.remove(i);
	subshapes.add(n - 1, s);
      }
    }
  }

  public void moveTo(int pos, DrawShape s) { 
    if (s != null && 
	subshapes != null) { 
      int n = subshapes.size();
      int i = subshapes.indexOf(s);
      if (i >= 0 && i < n && 
	  pos >= 0 && pos < n && 
	  i != pos) { 
	subshapes.remove(i);
	subshapes.add(pos, s);
      }
    }
  }

  public void removeAllShapes() {
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = n - 1; i >= 0; i--) {
	DrawShape s = subshapes.get(i);
	if (s != null) {
	  s.setParent(null);
	}
      }
      subshapes.clear();
    }
  }

  public int getShapeCount() {
    if (subshapes != null) {
      return subshapes.size();
    }
    return 0;
  }

  public ListIterator<DrawShape> allShapes() {
    if (subshapes != null) {
      return subshapes.listIterator();
    }
    return null;
  }

  public DrawShape findInRole(int role) {
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s.getRole() == role) {
	  return s;
	}
      }
    }
    return null;
  }

  public DrawShape findInRole(String rolename) {
    if (rolename != null && subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (rolename.equals(s.getRoleName())) {
	  return s;
	}
      }
    }
    return null;
  }

  public List<DrawShape> findAllInRole(int role) {
    if (subshapes != null) {
      List<DrawShape> result = new ArrayList<DrawShape>();
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s.getRole() == role) {
	  result.add(s);
	}
      }
      return result;
    }
    return null;
  }

  public List<DrawShape> findAllInRole(String rolename) {
    if (rolename != null && subshapes != null) {
      List<DrawShape> result = new ArrayList<DrawShape>();
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (rolename.equals(s.getRoleName())) {
	  result.add(s);
	}
      }
      return result;
    }
    return null;
  }

  public boolean isClipSubShapes() {
    return clipSubshapes;
  }

  public void setClipSubShapes(boolean b) {
    clipSubshapes = b;
  }

  public boolean isSubShapeClipped(DrawShape subshape) {
    return true;
  }

  // Is this a descendant of shape s?
  public boolean isDescendantOf(DrawShape s) {
    if (s != null) {
      DrawShape p = getParent();
      while (p != null) {
	if (p == s) {
	  return true;
	} else {
	  p = p.getParent();
	}
      }
    }
    return false;
  }

  public boolean isCloneLabel() {
    return cloneLabel;
  }

  public void setCloneLabel(boolean cloneLabel) {
    this.cloneLabel = cloneLabel;
  }

  public boolean isLabel(DrawShape shape) {
    return false;
  }

  public DrawShapeSearch.Result searchSubShapes(Point2D p) {
    return searchSubShapes(p, null, false);
  }

  public DrawShapeSearch.Result searchSubShapes(Point2D p, DrawShape exclude) {
    return searchSubShapes(p, exclude, false);
  }

  public DrawShapeSearch.Result searchSubShapes(Point2D p, 
						DrawShape exclude,
						boolean excludeOpenShape) {
    DrawShapeSearch.Result result = null;
    if (!isAtomic() && 
	subshapes != null && 
	p != null) {
      DrawShape shape;
      int pos = PointOutside;
      int n = subshapes.size();
      for (int i = n - 1; i >= 0; i--) {
	shape = subshapes.get(i);
	if (shape != null && 
	    shape.isVisible() && 
	    shape.isSearchable() &&
	    shape != exclude && 
	    !shape.isDescendantOf(exclude) && 
	    (!excludeOpenShape ||
	     !(shape instanceof OpenShape))) {
	  pos = shape.isInside(p);
	  if (pos != PointOutside) {
	    if (pos == PointOnMessage) {
	      result = new DrawShapeSearch.Result(shape, pos);
	    } else {
	      if (!shape.isAtomic()) {
		result = shape.searchSubShapes(p, exclude, excludeOpenShape);
	      }
	      if (result == null && 
		  pos != PointInBounds) {
		result = new DrawShapeSearch.Result(shape, pos);
	      } 
	    }
	    if (result != null) { 
	      return result;
	    }
	  }
	}
      }
    }
    return result;
  }

  public DrawShape findSubshape(Predicate pred) {
    return findSubshape(pred, false, subshapes);
  }

  public DrawShape findSubshape(Predicate pred, boolean recurse) {
    return findSubshape(pred, recurse, subshapes);
  }

  public static DrawShape findSubshape(Predicate pred, boolean recurse,
				       List<? extends DrawShape> shapes) {
    if (pred != null && 
	shapes != null) { 
      for (DrawShape shape : shapes) { 
	if (pred.evaluate(shape)) { 
	  return shape;
	} else if (recurse) {
	  DrawShape s = shape.findSubshape(pred, true); 
	  if (s != null) { 
	    return s; 
	  }
	}
      }
    }
    return null;
  }

  public List<DrawShape> findAllSubshapes(Predicate pred) {
    return findAllSubshapes(pred, false, subshapes, null);
  }

  public List<DrawShape> findAllSubshapes(Predicate pred, boolean recurse) {    
    return findAllSubshapes(pred, recurse, subshapes, null);
  }

  public List<DrawShape> findAllSubshapes(Predicate pred, boolean recurse, 
					  List<DrawShape> result) {
    return findAllSubshapes(pred, recurse, subshapes, result);
  }

  public static List<DrawShape> findAllSubshapes(Predicate pred, boolean recurse, 
						 List<? extends DrawShape> shapes,
						 List<DrawShape> result) {
    if (pred != null && 
	shapes != null) { 
      for (DrawShape shape : shapes) { 
	if (pred.evaluate(shape)) { 
	  if (result == null) { 
	    result = new ArrayList<DrawShape>();
	  }	
	  result.add(shape);
	} else if (recurse) {
	  List<DrawShape> subshapeList = shape.getShapes();
	  result = findAllSubshapes(pred, true, subshapeList, result); 	  
	}
      }
    }
    return result;
  }

  public DrawShape getSelectedSubShape() {
    return selectedSubshape;
  }

  public void setSelectedSubShape(DrawShape s) {
    selectedSubshape = s;
  }

  public boolean isFilled() {
    return filled;
  }

  public void setFilled(boolean filled) {
    this.filled = filled;
  }

  public static float getCusionSize() {
    return d;
  }

  public boolean isShadow() {
    return shadow;
  }

  public void setShadow(boolean shadow) {
    this.shadow = shadow;
  }

  public int getShadowWidth() {
    return shadowWidth;
  }

  public void setShadow(boolean shadow, int shadowWidth) {
    this.shadow = shadow;
    this.shadowWidth = shadowWidth;
  }

  public void setShadow(boolean shadow, boolean recurse) {
    this.shadow = shadow;
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	s.setShadow(shadow, true);
      }
    }
  }

  public boolean isFixedRatio() {
    return fixedRatio;
  }

  public void setFixedRatio(boolean fixedRatio) {
    this.fixedRatio = fixedRatio;
  }

  public boolean isBlured() {
    return blured;
  }

  public void setBlured(boolean blured) {
    this.blured = blured;
  }

  public DrawShape getParent() {
    return parent;
  }

  public void setParent(DrawShape c) {
    parent = c;
  }

  public DrawShape getTopShape() {
    DrawShape p1 = parent, p2 = parent;
    while (p1 != null) {
      p2 = p1; 
      p1 = p1.getParent();
    }
    return p2; 
  }

  public boolean isEditable() {
    return editable;
  }

  public void setEditable(boolean e) {
    editable = e;
  }

  public boolean isAllowRotation() {
    return allowRotation;
  }

  public void setAllowRotation(boolean allowRotation) {
    this.allowRotation = allowRotation;
  }

  public boolean isSymetricResize() {
    return symetricResize;
  }

  public void setSymetricResize(boolean symetricResize) {
    this.symetricResize = symetricResize;
  }

  public Point2D getRotationAnchor() {
    DrawShape parent = getParent();
    if (parent != null) {
      return parent.getRotationAnchor();
    } else {
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());
      float cx = (x1 + x2) / 2;
      float cy = (y1 + y2) / 2;
      return new Point2D.Float(cx, cy);
    }
  }

  public boolean isVisible() {
    return visible;
  }

  public void setVisible(boolean v) {
    visible = v;
  }

  public boolean isAtomic() {
    return atomic;
  }

  public void setAtomic(boolean a) {
    atomic = a;
  }

  public boolean isDetachable() {
    return detachable;
  }

  public void setDetachable(boolean s) {
    detachable = s;
  }

  public boolean isFitted() {
    return fitted;
  }

  public void setFitted(boolean f) {
    fitted = f;
  }

  public boolean isAcceptSubshape() {
    return acceptSubshape;
  }

  public void setAcceptSubshape(boolean acceptSubshape) {
    this.acceptSubshape = acceptSubshape;
  }

  public boolean isAcceptSubshape(DrawShape subshape) {
    return acceptSubshape && (subshape != null);
  }

  public boolean isCompound() {
    return false;
  }

  public boolean isEdgeSensitive() {
    return false;
  }

  public boolean isCornorSensitive() {
    return false;
  }

  public boolean isMovable() {
    return movable;
  }

  public void setMovable(boolean s) {
    movable = s;
  }

  public boolean isSticky() {
    return sticky;
  }

  public void setSticky(boolean s) {
    sticky = s;
  }

  public boolean isScalable() {
    return scalable;
  }

  public void setScalable(boolean s) {
    scalable = s;
  }

  public int getScaleMode() {
    return scaleMode;
  }

  public void setScaleMode(int scaleMode) {
    this.scaleMode = scaleMode;
  }

  public float canMoveX(float dx) {
    if (isMovable()) {
      DrawShape parent = getParent();
      if (parent != null && parent instanceof GraphNodeBase && !isDetachable()) {
	GraphNodeBase node = (GraphNodeBase) parent;
	if (this instanceof Label && node.isAllowLabelOutside()) {
	  return dx;
	}

	Rectangle2D b = parent.getBounds();
	float x = (float) b.getX();
	float w = (float) b.getWidth();
	Rectangle2D lb = getBounds();
	float lx = (float) lb.getX();
	float lw = (float) lb.getWidth();
	if (dx < 0) {
	  if (lx + dx >= x) {
	    return dx;
	  } else {
	    return (x - lx);
	  }
	}
	if (dx > 0) {
	  if (lx + lw + dx <= x + w) {
	    return dx;
	  } else {
	    return (x + w - lx - lw);
	  }
	}
      }
      return dx;
    } else {
      return 0;
    }
  }

  public float canMoveY(float dy) {
    if (isMovable()) {
      DrawShape parent = getParent();
      if (parent != null && parent instanceof GraphNodeBase && !isDetachable()) {
	if (getRole() == ROLE_NODE_TEMPLATE) {
	  return 0;
	}

	GraphNodeBase node = (GraphNodeBase) parent;
	if (this instanceof Label && node.isAllowLabelOutside()) {
	  return dy;
	}

	Rectangle2D b = parent.getBounds();
	float y = (float) b.getY();
	float h = (float) b.getHeight();
	Rectangle2D lb = getBounds();
	float ly = (float) lb.getY();
	float lh = (float) lb.getHeight();
	if (dy < 0) {
	  if (ly + dy >= y) {
	    return dy;
	  } else {
	    return (y - ly);
	  }
	}
	if (dy > 0) {
	  if (ly + lh + dy <= y + h) {
	    return dy;
	  } else {
	    return (y + h - ly - lh);
	  }
	}
      }
      return dy;
    } else {
      return 0;
    }
  }

  public boolean isResizable() {
    return resizable;
  }

  public void setResizable(boolean s) {
    resizable = s;
  }

  public boolean isSearchable() {
    return searchable;
  }

  public void setSearchable(boolean s) {
    searchable = s;
  }

  public boolean isSelectable() {
    return selectable;
  }

  public void setSelectable(boolean s) {
    selectable = s;
  }

  public Rectangle2D getBounds() {
    return bounds;
  }

  public boolean isZeroSize() {
    double x1 = p1.getX();
    double y1 = p1.getY();
    double x2 = p2.getX();
    double y2 = p2.getY();
    if (this instanceof OpenShape) {
      return (x1 == x2 && y1 == y2);
    } else {
      return (x1 == x2 || y1 == y2);
    }
  }

  public Rectangle2D getRepaintRect() {
    return framebounds;
  }

  public Rectangle2D getFrameBounds() {
    return framebounds;
  }

  public DrawAttr getDrawAttr() {
    if (attr == null && parent != null) {
      return parent.getDrawAttr();
    } else {
      return attr;
    }
  }

  public DrawAttr getThisDrawAttr() {
    return attr;
  }

  public void setDrawAttr(DrawAttr attr) {
    this.attr = attr;
  }

  public int getCursor() {
    return cursor;
  }

  public Point2D getCursorPosition() {
    if (cursor == 0) {
      return new Point2DFloat((float) p1.getX(), (float) p2.getY());
    } else {
      return new Point2DFloat((float) p2.getX(), (float) p2.getY());
    }
  }

  public DrawShape findLeftMostShape() {
    return this;
  }

  public DrawShape findRightMostShape() {
    return this;
  }

  public DrawShape setCursorLeftEnd() {
    cursor = 0;
    return this;
  }

  public DrawShape setCursorRightEnd() {
    cursor = 1;
    return this;
  }

  public DrawShape setCursor(int i) {
    if (i >= 0 && i <= 1) {
      cursor = i;
    } else {
      cursor = 0;
    }
    return this;
  }

  public DrawShape setCursor(Point2D p) {
    if (p != null) {
      if (p.getX() <= (bounds.getX() + bounds.getWidth() / 2.0)) {
	cursor = 0;
      } else {
	cursor = 1;
      }
    }
    return this;
  }

  public void incCursor() {
    if (cursor == 0) {
      cursor++;
    }
  }

  public void decCursor() {
    if (cursor == 1) {
      cursor--;
    }
  }

  public void showCursor(boolean b) {
    showcursor = b;
  }

  public boolean isShowCursor() {
    return showcursor;
  }

  public void showBorder(boolean b) {
    showborder = b;
  }

  public boolean isShowBorder() {
    return showborder;
  }

  public final void drawShape(Graphics2D g2) {
    drawShape(g2, DRAW_DEFAULT);
  }

  public final void drawShape(Graphics2D g2, DrawShape selectedShape) {
    drawShape(g2, (selectedShape == this ? DRAW_SELECTED : DRAW_DEFAULT),
	      selectedShape, null);
  }

  public final void drawShape(Graphics2D g2, 
			      DrawShape selectedShape,
			      Set<DrawShape> selectionSet) {
    drawShape(g2, (selectedShape == this ? DRAW_SELECTED : DRAW_DEFAULT),
	      selectedShape, selectionSet);
  }

  public final void drawShape(Graphics2D g2, int mode) {
    drawShape(g2, mode, null, null);
  }

  public final void drawShape(Graphics2D g2, int mode, 
			      DrawShape selectedShape) {
    drawShape(g2, mode, selectedShape, null);
  }

  public void drawShape(Graphics2D g2, int mode, 
			DrawShape selectedShape,
			Set<DrawShape> selectionSet) {
    if (debug) {
      System.out.println("DrawShapeBase.drawShape(): " + getClass().getName());
      System.out.println("   clip=" + g2.getClip());
      System.out.println("   bounds=" + bounds);
    }

    if (mode == DRAW_PRINT && isZeroSize()) {
      return;
    }

    boolean openshape = (this instanceof OpenShape);
    DrawAttr attr = getDrawAttr();
    Stroke oldStroke = null;
    Stroke newStroke = null;
    Color oldColor = null;
    Color newColor = null;

    if (mode == DRAW_OVER_HINT || 
	mode == DRAW_GRAPH_OVER_HINT || 
	mode == DRAW_HOTSPOT_HINT) {
      if (mode == DRAW_GRAPH_OVER_HINT && 
	  this instanceof GraphNodeBase) {
	newColor = overColorGraphNode;
      } else if (mode == DRAW_HOTSPOT_HINT) {
	newColor = hotspotColor;
      } else {
	newColor = overColor;
      }
      StrokeMap smap = StrokeMap.getInstance();
      if (attr != null) {
	newStroke = smap.findStroke(attr.getPenSize() + 4, attr.getLineCap(),
				    attr.getLineJoin(), attr.getLineStyle());
      } else {
	newStroke = smap.findStroke(5);
      }
    } else {
      if (mode == DRAW_INSERTION_HINT) {
	newColor = insertionHintColor;
      } else {
	if (attr != null) {
	  newColor = attr.getPenColor();
	} else {
	  newColor = DrawAttr.getDefaultPenColor();
	}
      }

      if (attr != null) {
	StrokeMap smap = StrokeMap.getInstance();
	newStroke = smap.findStroke(attr.getPenSize(), attr.getLineCap(), 
				    attr.getLineJoin(), attr.getLineStyle());
      }
    }

    // transformation must be done before all drawing
    AffineTransform oldTx = null;
    if (at != null) {
      oldTx = g2.getTransform();
      Point2D anchor = getRotationAnchor();
      int ax = (int) anchor.getX();
      int ay = (int) anchor.getY();
      g2.translate(ax, ay);
      g2.transform(at);
      g2.translate(-ax, -ay);
    }

    if (newColor != null) {
      oldColor = g2.getColor();
      g2.setColor(newColor);
    }
    if (newStroke != null) {
      oldStroke = g2.getStroke();
      g2.setStroke(newStroke);
    }

    if (mode == DRAW_OVER_HINT || 
	mode == DRAW_GRAPH_OVER_HINT || 
	mode == DRAW_HOTSPOT_HINT) {
      drawOverHint(g2);
    } else {
      draw(g2);
    }

    if (mode == DRAW_HOTSPOT_HINT) {
      drawHotSpots(g2);
    }

    if (shadow && shadowWidth > 0) { 
      DrawingUtilities.drawDropShadow(g2, getOutlineShape(), !openshape, shadowWidth);
    }

    if (blured) {
      //DrawingUtilities.paintBlurFrame(g2, getBoundingRect());
      DrawingUtilities.paintBlurFrame(g2, getOutlineShape(), !openshape);
    } 

    if (mode != DRAW_HOTSPOT_HINT) {
      // drawSubShapes(g2, mode, selectedShape);
      int smode = DRAW_DEFAULT;
      if (mode == DRAW_PRINT) {
	smode = DRAW_PRINT;
      }
      drawSubShapes(g2, smode, selectedShape, selectionSet);
    }

    if (mode == DRAW_SELECTED || this == selectedShape) {
      drawFrame(g2);
    }

    if (selectionSet != null && 
	selectionSet.contains(this)) {
      DrawingUtilities.drawGroupFrame(g2, getBounds(), true);
    }

    if (message != null) { 
      Icon icon = message.getIcon();
      if (icon != null) { 
	icon.paintIcon(null, g2, (int) p1.getX() - framePad, (int) p1.getY()); 
      }
    }

    if (oldTx != null) {
      g2.setTransform(oldTx);
    }

    if (debug_drawframe) {
      // debugging
      DrawingUtilities.drawFrame(g2, framebounds, debugFrameColor1, null, false);
      DrawingUtilities.drawFrame(g2, bounds, debugFrameColor2, null, false);
    }

    if (oldColor != null) {
      g2.setColor(oldColor);
    }
    if (oldStroke != null && newStroke != null) {
      g2.setStroke(oldStroke);
    }
  }

  protected void fillBackground(Graphics2D g2, float x, float y, float w, float h,
				Shape shape) {
    fillBackground(g2, attr, x, y, w, h, shape); 
  }

  protected static void fillBackground(Graphics2D g2, DrawAttr attr, 
				       float x, float y, float w, float h,
				       Shape shape) {
    Color c = g2.getColor();
    if (attr != null) {
      Color fc = attr.getFillColor();
      Color gc1 = attr.getGradientColor1();
      Color gc2 = attr.getGradientColor2();
      if (gc1 == null || gc2 == null) { 
	g2.setColor(fc);
      } else { 
	Paint paint; 
	int gm = attr.getGradientMode();
	float[] dist = {0.0f, 1.0f};
	Color[] colors = {gc1, gc2};
	Point2D start, end;
	if (gm >= DrawAttr.RADIAL_GRADIENT_CENTER && 
	    gm <= DrawAttr.RADIAL_GRADIENT_LOWER_RIGHT) { 
	  float radius = (float) Math.sqrt(h * h + w * w); 
	  switch (gm) { 
	  case DrawAttr.RADIAL_GRADIENT_UPPER_LEFT:
	    start = new Point2D.Float(x, y);
	    end = new Point2D.Float(x + w, y + h);
	    break;
	  case DrawAttr.RADIAL_GRADIENT_UPPER_RIGHT:
	    start = new Point2D.Float(x + w, y);
	    end = new Point2D.Float(x, y + h);
	    break;
	  case DrawAttr.RADIAL_GRADIENT_LOWER_LEFT:
	    start = new Point2D.Float(x, y + h);
	    end = new Point2D.Float(x + w, y);
	    break;
	  case DrawAttr.RADIAL_GRADIENT_LOWER_RIGHT:
	    start = new Point2D.Float(x + w, y + h);
	    end = new Point2D.Float(x, y);
	    break;	      
	  default:
	    start = new Point2D.Float(x + w / 2, y + h / 2);
	    end = new Point2D.Float(x, y);
	    radius /= 2; 
	  }
	  paint = new RadialGradientPaint(start, radius, dist, colors);
	} else { 
	  if (attr.isGradientReflect()) { 
	    switch (gm) { 
	    case DrawAttr.LINEAR_GRADIENT_TOP_TO_BOTTOM:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x, y + h / 2);
	      break; 
	    case DrawAttr.LINEAR_GRADIENT_DIAGNAL_DOWN:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x + w / 2, y + h / 2);
	      break; 
	    case DrawAttr.LINEAR_GRADIENT_DIAGNAL_UP:
	      start = new Point2D.Float(x, y + h);
	      end = new Point2D.Float(x + w / 2, y + h / 2);
	      break; 
	    default:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x + w / 2, y);
	    }
	    paint = new GradientPaint(start, gc1, end, gc2, true);
	  } else { 
	    switch (gm) { 
	    case DrawAttr.LINEAR_GRADIENT_TOP_TO_BOTTOM:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x, y + h);
	      break; 
	    case DrawAttr.LINEAR_GRADIENT_DIAGNAL_DOWN:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x + w, y + h);
	      break; 
	    case DrawAttr.LINEAR_GRADIENT_DIAGNAL_UP:
	      start = new Point2D.Float(x, y + h);
	      end = new Point2D.Float(x + w, y);
	      break; 
	    default:
	      start = new Point2D.Float(x, y);
	      end = new Point2D.Float(x + w, y);
	    }	   
	    paint = new GradientPaint(start, gc1, end, gc2);
	  }
	}
	g2.setPaint(paint);
      }
    }    
    g2.fill(shape);
    g2.setColor(c);
  }

  protected void drawHotSpots(Graphics2D g2) {
  }

  // used in drawSubshapes() 
  public Rectangle2D getClipRect() {
    return getEndRect();
  }

  public Rectangle2D getEndRect() {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    return new Rectangle2DFloat(x, y, w, h);
  }

  public void drawSubShapes(Graphics2D g2, int mode, DrawShape selectedShape,
			    Set<DrawShape> selectionSet) {
    drawSubShapes(g2, mode, selectedShape, selectionSet, false);
  }

  public void drawSubShapes(Graphics2D g2, int mode, 
			    DrawShape selectedShape,
			    Set<DrawShape> selectionSet, 
			    boolean reverseOrder) {
    if (debug) {
      System.out.println("DrawShapeBase.drawSubShapes(): clipSubshapes=" + clipSubshapes);
      if (subshapes == null) {
	System.out.println("\tsubshapes==null");
      } else {
	System.out.println("\t#subshapes=" + subshapes.size());
      }
    }

    if (subshapes != null) {
      int n = subshapes.size();
      Rectangle2D oldClipRect = null;
      Rectangle2D newClipRect = getClipRect();
      Shape clip = g2.getClip();
      if (clip != null) { 
	if (clip instanceof Rectangle2D) { 
	  oldClipRect = (Rectangle2D) clip;
	} else { 
	  oldClipRect = clip.getBounds2D();
	}
      }
      if (oldClipRect != null) { 
	Rectangle2D.intersect(oldClipRect, newClipRect, newClipRect);
      }
      int i = (reverseOrder ? n - 1 : 0); 
      while (reverseOrder ? i >= 0 : i < n) {
	DrawShape s = subshapes.get(i);
	if (s.isVisible()) {
	  if (!clipSubshapes || !isSubShapeClipped(s)) {
	    // template shape and nodes on boundary 
	    g2.setClip(null);
	  } else {
	    g2.setClip(newClipRect); 
	  }
	  s.drawShape(g2, mode, selectedShape, selectionSet);
	}
	if (reverseOrder) {
	  i--;
	} else {
	  i++;
	}
      }
      g2.setClip(oldClipRect);

    }
  }

  abstract public void draw(Graphics2D g2);

  public void drawOverHint(Graphics2D g2) {
    draw(g2);
  }

  public void drawCursor(Graphics2D g2) {
    float x, y;
    if (cursor == 1) {
      x = (float) (bounds.getX() + bounds.getWidth() - 1.0);
    } else {
      x = (float) bounds.getX();
    }
    y = (float) bounds.getY();
    Color save_color = g2.getColor();
    g2.setColor(cursorcolor);
    g2.draw(new Line2D.Float(x, y, x, y + (float) bounds.getHeight()));
    g2.setColor(save_color);
  }

  public int isInside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  public int isInside(float px, float py) {
    int result = PointOutside;
    float x1 = (float) framebounds.getX();
    float y1 = (float) framebounds.getY();
    float x2 = (float) (framebounds.getX() + framebounds.getWidth());
    float y2 = (float) (framebounds.getY() + framebounds.getHeight());

    if ((px >= x1 - d) && 
	(px <= x2 + d) && 
	(py >= y1 - d) && 
	(py <= y2 + d)) {
      result = PointInBounds;
    }

    x1 = (float) Math.min(p1.getX(), p2.getX());
    y1 = (float) Math.min(p1.getY(), p2.getY());
    x2 = (float) Math.max(p1.getX(), p2.getX());
    y2 = (float) Math.max(p1.getY(), p2.getY());
    float cx = (x1 + x2) / 2;
    float cy = (y1 + y2) / 2;
    float tx = px;
    float ty = py;
    if ((x2 - x1) < (3 * d)) {
      x1 -= d;
      x2 += d;
    }
    if ((y2 - y1) < (3 * d)) {
      y1 -= d;
      y2 += d;
    }
    if (at != null) {
      float[] src = { tx - cx, ty - cy };
      float[] dst = new float[2];
      if (inv != null) {
	inv.transform(src, 0, dst, 0, 1);
	tx = dst[0] + cx;
	ty = dst[1] + cy;
      }
    }
    
    if ((tx >= x1 - 2.0f) && 
	(tx <= x2 + 2.0f) && 
	(ty >= y1 - 2.0f) && 
	(ty <= y2 + 2.0f)) {
      result = PointInside;
      float dx1 = Math.abs(tx - x1);
      float dx2 = Math.abs(tx - x2);
      float dy1 = Math.abs(ty - y1);
      float dy2 = Math.abs(ty - y2);
      if (dx1 <= d) {
	if (dy1 <= d) {
	  result = PointNWCorner;
	} else if (dy2 <= d) {
	  result = PointSWCorner;
	} else {
	  result = PointWestEdge;
	}
      } else if (dx2 <= d) {
	if (dy1 <= d) {
	  result = PointNECorner;
	} else if (dy2 <= d) {
	  result = PointSECorner;
	} else {
	  result = PointEastEdge;
	}
      } else if (dy1 <= d) {
	result = PointNorthEdge;
      } else if (dy2 <= d) {
	result = PointSouthEdge;
      }
      
    } else if (this instanceof Rotatable && 
	       allowRotation) {
      if (isNearPoint(tx, ty, (x1 + x2) / 2, y1 - handleLength)) {
	result = PointOnRotationHandle;
      }     
    } 
    return result;
  }

  public boolean isOnMessage(float px, float py) {
    if (message != null) { 
      Icon icon = message.getIcon();
      if (icon != null) { 
	float x = (float) p1.getX() - framePad;
	float y = (float) p1.getY();
	if (px >= x && px < (x + icon.getIconWidth()) && 
	    py >= y && py < (y + icon.getIconHeight())) { 
	  return true; 
	}
      }
    }
    return false;
  }

  public boolean isOutside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isOutside(px, py);
    } else {
      return true;
    }
  }

  public boolean isOutside(float px, float py) {
    float x1 = (float) bounds.getX();
    float y1 = (float) bounds.getY();
    float x2 = (float) (bounds.getX() + bounds.getWidth());
    float y2 = (float) (bounds.getY() + bounds.getHeight());
    int margin = 5; 

    if ((px < x1 - margin) ||
	(px > x2 + margin) ||
	(py < y1 - margin) || 
	(py > y2 + margin)) {
      return true; 
    }
    return false;
  }

  public int isOnHotSpot(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isOnHotSpot(px, py);
    } else {
      return HOTSPOT_NONE;
    }
  }

  public int isOnHotSpot(float px, float py) {
    return HOTSPOT_NONE;
  }

  public boolean isNearHotSpot(float x1, float y1, float x2, float y2) {
    float dx = Math.abs(x2 - x1);
    float dy = Math.abs(y2 - y1);
    return (dx <= HOTSPOT_SIZE && dy <= HOTSPOT_SIZE);
  }

  public boolean isNearPoint(float x1, float y1, float x2, float y2) {
    float dx = Math.abs(x2 - x1);
    float dy = Math.abs(y2 - y1);
    return (dx <= d && dy <= d);
  }

  public boolean isNearLine(Point2D end1, Point2D end2, float px, float py) {
    if (end1 != null && end2 != null) {
      Line2D line = new Line2D.Float(end1, end2);
      double dist = line.ptLineDist(px, py);
      return (dist <= d);
    }
    return false;
  }

  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    drawShape(g2);
  }

  public void drawOutline(Graphics2D g2) {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    drawOutline(g2, x, y, x + w, y + h);
  }

  public void drawSubShapesOutline(Graphics2D g2) {
    if (subshapes != null) {
      if (clipSubshapes) {
	g2.setClip(getClipRect());
      }
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = (DrawShape) subshapes.get(i);
	if (s.isVisible()) {
	  s.drawOutline(g2);
	}
      }
      if (clipSubshapes) {
	g2.setClip(null);
      }
    }
  }

  public void drawFrame(Graphics2D g2) {
    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.penSize;
    }

    g2.setStroke(markStroke);
    int marksize = DrawingUtilities.getMarkSize(penSize);
    Color frameColor = defaultFrameColor;     
    DrawingUtilities.drawRectFrame(g2, p1, p2, 
				   needFrameRect, true,
				   (this instanceof Rotatable) && allowRotation, 
				   frameMarkFilled,
				   marksize, frameColor, anchorColor);
    drawFrameEx(g2, marksize, frameMarkFilled, defaultFrameColor);
  }

  public void drawFrameEx(Graphics2D g2, float marksize, 
			  boolean fill, Color frameColor) {
  }

  public Rectangle2D getBoundingRect() {
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float h = (float) Math.abs(p2.getY() - p1.getY());
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    return new Rectangle2D.Float(x, y, w, h);
  }

  public Shape getOutlineShape() {
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float h = (float) Math.abs(p2.getY() - p1.getY());
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    return new Rectangle2D.Float(x, y, w, h);
  }

  public void adjustBounds() {
    if (bounds != null) {
      float penSize = 1;
      if (attr != null) {
	penSize = attr.penSize;
      }
      bounds.setRect((float) bounds.getX() - (penSize / 2), 
		     (float) bounds.getY() - (penSize / 2), 
		     (float) bounds.getWidth() + penSize, 
		     (float) bounds.getHeight() + penSize);
      if (framebounds != null) {
	framebounds.setRect((float) framebounds.getX() - (penSize / 2) - framePad, 
			    (float) framebounds.getY() - (penSize / 2) - framePad,
			    (float) framebounds.getWidth() + penSize + framePad * 2,
			    (float) framebounds.getHeight() + penSize + framePad * 2);
      }
    }
  }

  public void setEnds(Point2D p1, Point2D p2) {
    if (p1 != null && p2 != null) { 
      setEnds((float) p1.getX(), (float) p1.getY(), 
	      (float) p2.getX(), (float) p2.getY());
    }
  }

  public Point2D getPosition() {
    return p1;
  }

  public final void setPosition(Point2D p) {
    if (p != null) {
      setPosition((float) p.getX(), (float) p.getY());
    }
  }

  public void setPosition(float x, float y) {
    float dx = x - (float) p1.getX();
    float dy = y - (float) p1.getY();
    move(dx, dy);
  }

  public void setCenterPosition(Point2D p) {
    if (p != null) {
      setCenterPosition((float) p.getX(), (float) p.getY());
    }
  }

  public void setCenterPosition(float x, float y) {
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float h = (float) Math.abs(p2.getY() - p1.getY());
    float x1 = (float) Math.min(p1.getX(), p2.getX());
    float y1 = (float) Math.min(p1.getY(), p2.getY());
    float cx = x1 + w / 2;
    float cy = y1 + h / 2;
    float dx = x - cx;
    float dy = y - cy;
    move(dx, dy);
  }

  public Point2D getCenterPosition() {
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float h = (float) Math.abs(p2.getY() - p1.getY());
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    return new Point2DFloat(x + w / 2, y + h / 2);
  }

  public void move(float dx, float dy) {
    //System.out.println("DrawShapeBase.move() " + getClass().getName() + " " + bounds);

    p1.setLocation(p1.getX() + dx, p1.getY() + dy);
    p2.setLocation(p2.getX() + dx, p2.getY() + dy);
    if (subshapes != null) {
      int count = subshapes.size();
      for (int i = 0; i < count; i++) {
	DrawShape s = (DrawShape) subshapes.get(i);
	s.move(dx, dy);
      }
    }
    calculateBounds();
  }

  public void moveNotify(float dx, float dy) {
    move(dx, dy);
    if (parent != null) {
      parent.componentMoved(this, dx, dy);
    }
  }

  public Point2D getEnd1() {
    return p1;
  }

  public Point2D getEnd2() {
    return p2;
  }

  public float getShapeWidth() {
    return (float) Math.abs(p1.getX() - p2.getX());
  }

  public float getShapeHeight() {
    return (float) Math.abs(p1.getY() - p2.getY());
  }

  public void setEnds(float x1, float y1, float x2, float y2) {
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
    calculateBounds();
    normalize();
  }

  public void scaleSubshapes(float x1, float y1, float x2, float y2) {
    float xmin = (float) Math.min(p1.getX(), p2.getX());
    float ymin = (float) Math.min(p1.getY(), p2.getY());
    float xmax = (float) Math.max(p1.getX(), p2.getX());
    float ymax = (float) Math.max(p1.getY(), p2.getY());

    float x0 = Math.min(x1, x2);
    float y0 = Math.min(y1, y2);
    float sx = 1;
    float sy = 1;
    if (x1 == x2 && y1 == y2) {
      sx = 0;
      sy = 0;
    } else {
      if (xmax != xmin) {
	sx = Math.abs(x2 - x1) / (xmax - xmin);
      }
      if (ymax != ymin) {
	sy = Math.abs(y2 - y1) / (ymax - ymin);
      }
    }

    float xx1 = x0 + ((float) p1.getX() - xmin) * sx;
    float yy1 = y0 + ((float) p1.getY() - ymin) * sy;
    float xx2 = x0 + ((float) p2.getX() - xmin) * sx;
    float yy2 = y0 + ((float) p2.getY() - ymin) * sy;

    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape shape = (DrawShape) subshapes.get(i);
	Point2D sp1 = shape.getEnd1();
	Point2D sp2 = shape.getEnd2();
	float sx1 = (float) sp1.getX();
	float sy1 = (float) sp1.getY();
	float sx2 = (float) sp2.getX();
	float sy2 = (float) sp2.getY();
	float nx1 = (sx1 - xmin) * sx + x0;
	float ny1 = (sy1 - ymin) * sy + y0;
	float nx2 = (sx2 - xmin) * sx + x0;
	float ny2 = (sy2 - ymin) * sy + y0;
	if ((scaleMode & SCALE_EXCLUDE_SUBSHAPE) != 0) {
	  nx2 = nx1 + (sx2 - sx1);
	  ny2 = ny1 + (sy2 - sy1);
	}
	shape.setEndsAdj(nx1, ny1, nx2, ny2);
      }
    }

    scaleEx(sx, sy);
  }

  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    scaleSubshapes(x1, y1, x2, y2);
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
    calculateBounds();
    normalize();
    // setEnds(xx1, yy1, xx2, yy2);
  }

  protected void scaleEx(float sx, float sy) {
  }

  public void scale(float sx, float sy) {
    float x1 = (float) Math.min(p1.getX(), p2.getX());
    float y1 = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float h = (float) Math.abs(p2.getY() - p1.getY());
    setEndsAdj(x1, y1, x1 + w * sx, y1 + h * sy);

    float s = Math.min(sx, sy); 
    shadowWidth = (int) Math.floor(shadowWidth * s); 
  }

  public void scaleEnds(float x1, float y1, float x2, float y2) {
    setEndsAdj(x1, y1, x2, y2);
  }

  public void setEnd1(float x, float y) {
    moveEnd1(x - (float) p1.getX(), y - (float) p1.getY());
  }

  public void setEnd2(float x, float y) {
    moveEnd2(x - (float) p2.getX(), y - (float) p2.getY());
  }

  public void moveEnd1(float dx, float dy) {
    p1.setLocation(p1.getX() + dx, p1.getY() + dy);
    if (isCompound()) {
      CompoundDrawShape container = (CompoundDrawShape) this;
      container.doLayout();
      container.fitComponents();
    } 
    calculateBounds();
  }

  public void moveEnd2(float dx, float dy) {
    p2.setLocation(p2.getX() + dx, p2.getY() + dy);
    if (isCompound()) {
      CompoundDrawShape container = (CompoundDrawShape) this;
      container.doLayout();
      container.fitComponents();
    } 
    calculateBounds();
  }

  public final void moveCorner(int pos, float dx, float dy) {
    moveCorner(pos, dx, dy, true);
  }

  public void moveCorner(int pos, float dx, float dy, boolean scaleSubshapes) {
    if (at != null) {
      float[] src = { dx, dy };
      float[] dst = new float[2];
      if (inv != null) {
	inv.transform(src, 0, dst, 0, 1);
	dx = dst[0];
	dy = dst[1];
      }
    }

    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    switch (pos) {
    case PointNECorner:
      if (symetricResize) {
	x1 -= dx;
	y1 += dy;
	x2 += dx;
	y2 -= dy;
      } else {
	y1 += dy;
	x2 += dx;
      }
      break;

    case PointNWCorner:
      x1 += dx;
      y1 += dy;
      if (symetricResize) {
	x2 -= dx;
	y2 -= dy;
      }
      break;

    case PointSECorner:
      x2 += dx;
      y2 += dy;
      if (symetricResize) {
	x1 -= dx;
	y1 -= dy;
      }
      break;

    case PointSWCorner:
      if (symetricResize) {
	x1 += dx;
	y1 -= dy;
	x2 -= dx;
	y2 += dy;
      } else {
	x1 += dx;
	y2 += dy;
      }
      break;
    }

    if (scaleSubshapes && getShapeCount() > 0) {
      scaleSubshapes(x1, y1, x2, y2);
    }
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);

    doLayout();
    calculateBounds();
  }

  public void scaleComponents(float sx, float sy) {
    if (subshapes != null) {
      Point2D anchor = getRotationAnchor();
      /*
       * Point2D anchor; if (symetricResize) { } else { }
       */
      float ax = (float) anchor.getX();
      float ay = (float) anchor.getY();
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape shape = (DrawShape) subshapes.get(i);
	Point2D sp1 = shape.getEnd1();
	Point2D sp2 = shape.getEnd2();
	float x1 = (float) sp1.getX();
	float y1 = (float) sp1.getY();
	float x2 = (float) sp2.getX();
	float y2 = (float) sp2.getY();
	float nx1 = (x1 - ax) * sx + ax;
	float ny1 = (y1 - ay) * sy + ay;
	float nx2 = (x2 - ax) * sx + ax;
	float ny2 = (y2 - ay) * sy + ay;
	shape.setEnds(nx1, ny1, nx2, ny2);
      }
      layoutComponents();
    }
  }

  public final void moveEdge(int pos, float dx, float dy) {
    moveEdge(pos, dx, dy, false);
  }

  public void moveEdge(int pos, float dx, float dy, boolean scaleSubshapes) {
    if (at != null) {
      float[] src = { dx, dy };
      float[] dst = new float[2];
      if (inv != null) {
	inv.transform(src, 0, dst, 0, 1);
	dx = dst[0];
	dy = dst[1];
      }
    }

    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    switch (pos) {
    case PointNorthEdge:
      // p1.setLocation(p1.getX(), p1.getY() + dy);
      y1 += dy;
      if (symetricResize) {
	// p2.setLocation(p2.getX(), p2.getY() - dy);
	y2 -= dy;
      }
      break;

    case PointSouthEdge:
      // p2.setLocation(p2.getX(), p2.getY() + dy);
      y2 += dy;
      if (symetricResize) {
	// p1.setLocation(p1.getX(), p1.getY() - dy);
	y1 -= dy;
      }
      break;

    case PointEastEdge:
      // p2.setLocation(p2.getX() + dx, p2.getY());
      x2 += dx;
      if (symetricResize) {
	// p1.setLocation(p1.getX() - dx, p1.getY());
	x1 -= dy;
      }
      break;

    case PointWestEdge:
      // p1.setLocation(p1.getX() + dx, p1.getY());
      x1 += dx;
      if (symetricResize) {
	// p2.setLocation(p2.getX() - dx, p2.getY());
	x2 -= dx;
      }
      break;
    }

    // if ((scaleMode & SCALE_EXCLUDE_SUBSHAPE) == 0 &&
    if (scaleSubshapes && getShapeCount() > 0) {
      scaleSubshapes(x1, y1, x2, y2);
    }
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
    doLayout();
    calculateBounds();
  }

  public void moveCircumference(float px, float py) {
  }

  public Arrow getHeadArrow() {
    return head;
  }

  public Arrow getTailArrow() {
    return tail;
  }

  public void setHeadArrow(Arrow head) {
    this.head = head;
    if (head != null) {
      head.setOwner(this);
    }
  }

  public void setTailArrow(Arrow tail) {
    this.tail = tail;
    if (tail != null) {
      tail.setOwner(this);
    }
  }

  public void setArrowPosition() {
  }

  public void setTailArrow(ArrowAttr tailArrowAttr) {
    if (this instanceof OpenShape) {
      if (tailArrowAttr != null) {
	tail = new Arrow(tailArrowAttr);
	tail.setOwner(this);
      } else {
	tail = null;
      }
    }
  }

  public void setHeadArrow(ArrowAttr headArrowAttr) {
    if (this instanceof OpenShape) {
      if (headArrowAttr != null) {
	head = new Arrow(headArrowAttr);
	head.setOwner(this);
      } else {
	head = null;
      }
    }
  }

  public void addArrows(ArrowAttr headArrowAttr, ArrowAttr tailArrowAttr) {
    setTailArrow(tailArrowAttr);
    setHeadArrow(headArrowAttr);
  }

  public Object getCustomAttribute(String key) { 
    if (customAttr != null && 
	key != null) { 
      return customAttr.get(key); 
    }
    return null;
  }

  public void setCustomAttribute(String key, Object value) { 
    if (key != null) { 
      if (value != null) { 
	if (customAttr == null) {
	  customAttr = new HashMap<String, Object>();
	}
	customAttr.put(key, value); 
      } else { 
	if (customAttr != null) {
	  customAttr.remove(key);
	}
      }
    }
  }

  public void recalculateBounds() {
    calculateBounds();
  }

  public void setBounds(float x1, float y1, float x2, float y2) {
    float x0 = Math.min(x1, x2); 
    float y0 = Math.min(y1, y2); 
    float w = Math.abs(x2 - x1); 
    float h = Math.abs(y2 - y1); 

    bounds.setRect(x0, y0, w, h);
    float dy = (allowRotation ? handleLength : 0);
    framebounds.setRect(x0 - d, y0 - d - dy, w + 2 * d, h + 2 * d);
    adjustBounds();
  }

  public Rectangle2D getSubshapeBounds() {
    Rectangle2D bounds = null;
    if (subshapes != null) {
      bounds = DrawShapeUtil.getShapeBounds(subshapes);
    }
    return bounds;
  }

  public void calculateBounds() {
    calculateBounds(true);
  }

  public void calculateBounds(boolean includeSubshapes) {
    float dy = (allowRotation ? handleLength : 0);
    float x1 = (float) Math.min(p1.getX(), p2.getX());
    float y1 = (float) Math.min(p1.getY(), p2.getY());
    float x2 = (float) Math.max(p1.getX(), p2.getX());
    float y2 = (float) Math.max(p1.getY(), p2.getY());
    float w = x2 - x1;
    float h = y2 - y1;
    bounds.setRect(x1, y1, w, h);
    if (includeSubshapes && 
	subshapes != null && 
	subshapes.size() > 0) {
      Rectangle2D sub = getSubshapeBounds();
      if (sub != null) {
	Rectangle2D.union(bounds, sub, bounds);
      }
    }

    x1 = (float) bounds.getX();
    y1 = (float) bounds.getY();
    w = (float) bounds.getWidth();
    h = (float) bounds.getHeight();
    float cx = x1 + w / 2;
    float cy = y1 + h / 2;

    if (at == null) {
      framebounds.setRect(x1 - d, y1 - dy - d, w + 2 * d, h + dy + 2 * d);
    } else {
      float[] src = { - w / 2, - h / 2, 
		      - w / 2,   h / 2, 
		        w / 2, - h / 2, 
		        w / 2,   h / 2, 
		      - w / 2, - h / 2 - dy, 
		      - w / 2,   h / 2, 
		        w / 2, - h / 2 - dy, 
		        w / 2,   h / 2 };
      float[] dst = new float[16];
      at.transform(src, 0, dst, 0, 8);
      float xMin, xMax, yMin, yMax;
      xMin = xMax = dst[0];
      yMin = yMax = dst[1];
      int i;
      for (i = 1; i <= 3; i++) {
	float x = dst[2 * i];
	float y = dst[2 * i + 1];
	xMin = Math.min(x, xMin);
	xMax = Math.max(x, xMax);
	yMin = Math.min(y, yMin);
	yMax = Math.max(y, yMax);
      }
      bounds.setRect(cx + xMin - 1, cy + yMin - 1, xMax - xMin + 1, yMax - yMin + 1);

      xMin = xMax = dst[8];
      yMin = yMax = dst[9];
      for (i = 5; i <= 7; i++) {
	float x = dst[2 * i];
	float y = dst[2 * i + 1];
	xMin = Math.min(x, xMin);
	xMax = Math.max(x, xMax);
	yMin = Math.min(y, yMin);
	yMax = Math.max(y, yMax);
      }
      framebounds.setRect(cx + xMin - 1 - d, cy + yMin - 1 - d, 
			  xMax - xMin + 1 + 2 * d, yMax - yMin + 1 + 2 * d);
    }
    adjustBounds();
  }

  public AffineTransform getTransform() {
    return at;
  }

  public float getRotationAngle() {
    return angle;
  }

  public void setRotationAngle(float angle) {
    this.angle = angle;
    if (angle != 0) {
      if (at == null) {
	at = new AffineTransform();
      }
      at.setToRotation(angle);
      try {
	inv = at.createInverse();
      } catch (Exception e) {
	inv = null;
      }
    } else {
      at = inv = null;
    }
    calculateBounds();
  }

  public void setToRotation(float vx, float vy) {
    Point2D anchor = getRotationAnchor();
    float x = (float) anchor.getX();
    float y = (float) anchor.getY();
    float t = (float) DrawingUtilities.calculateRotationAngle(x, y, vx, vy);
    float a = t + ANGLE_90;
    if (a >= ANGLE_360) {
      a -= ANGLE_360;
    }
    setRotationAngle(a);
  }

  public String toString() {
    return ("DrawShape: " + this.getClass().getName() + " bounds=" + bounds + 
	    " p1=" + p1 + " p2=" + p2 + " visible=" + visible + " editable=" + editable + 
	    " movable=" + movable + " attr=" + attr);
  }

  public String getName() {
    if (name != null) {
      return name;
    } else {
      return getClass().getName();
    }
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getRoleName() {
    return roleName;
  }

  public void setRoleName(String rname) {
    roleName = rname;
  }

  public String getTemplateName() {
    return templateName;
  }

  public void setTemplateName(String tname) {
    templateName = tname;
  }

  public String getDisplayName() {
    return displayName;
  }

  public void setDisplayName(String tname) {
    displayName = tname;
  }

  public String getAnnotation() {
    return annotation;
  }

  public void setAnnotation(String annotation) {
    this.annotation = annotation;
  }

  public int getRole() {
    return role;
  }

  public void setRole(int role) {
    this.role = role;
  }

  public int getFlag() {
    return flag;
  }

  public void setFlag(int flag) {
    this.flag = flag;
  }

  public int getDeco() {
    return deco;
  }

  public void setDeco(int deco) {
    this.deco = deco;
  }

  public DrawShapeMessage getMessage() {
    return message;
  }

  public void setMessage(DrawShapeMessage message) {
    this.message = message;
  }

  public int getDisplayMode() { 
    return displayMode;
  }

  public void setDisplayMode(int displayMode) { 
    this.displayMode = displayMode;
  }

  public ModelElement getModel() {
    return model;
  }

  public void setModel(ModelElement model) {
    // System.out.println("DrawShapeBase.setModel() model=" + model);

    if (this.model != null) {
      ModelRepository rep = ModelRepository.getInstance();
      rep.removeElement(this.model);
    }

    this.model = model;
    if (modelListener != null) {
      modelListener.set(model, this);
    }
  }

  public ModelListener getModelListener() {
    return modelListener;
  }

  public void setModelListener(ModelListener ml) {
    // System.out.println("DrawShapeBase.setModelListener() ml=" + ml);

    modelListener = ml;
    if (modelListener != null) {
      modelListener.set(model, this);
    }
  }

  public void updateModel() {
    if (modelListener != null) { 
      modelListener.updateModel();
    }
  }

  public void updateShape() {
    if (modelListener != null) { 
      modelListener.updateShape();
    }
  }

  public String getBoundsString() {
    return "[" + (int) bounds.getX() + ", " + (int) bounds.getY() + ", " + 
      (int) bounds.getWidth() + ", " + (int) bounds.getHeight() + "]";
  }

  public void calculateComponentBounds() {
    Rectangle2D sub = getSubshapeBounds();

    if (debug) 
      System.out.println("DrawShapeBase.calculateComponentBounds() bounds=" + sub);
    
    if (sub != null) {
      bounds = sub;
    } else {
      bounds.setRect(0, 0, 0, 0);
    }
    framebounds.setRect(bounds.getX() - d, bounds.getY() - d - handleLength,
			bounds.getWidth() + 2 * d, bounds.getHeight() + 2 * d + handleLength);
  }

  public void fitComponents() {
    calculateComponentBounds();
    p1.setLocation((float) bounds.getX(), (float) bounds.getY());
    p2.setLocation((float) (bounds.getX() + bounds.getWidth()), 
		   (float) (bounds.getY() + bounds.getHeight()));

    if (debug) 
      System.out.println("DrawShapeBase.fitComponents() bounds=" + getBoundsString());
  }

  public void layoutComponents() {
    fitComponents();
  }

  public void doLayout() {
    if (fitted) { 
      fitComponents();
    } else { 
      layoutComponents();
    }
  }

  public void componentMoved(DrawShape comp, float dx, float dy) {
    if (comp != null && fitted) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape shape = subshapes.get(i);
	if (shape != comp) {
	  shape.move(dx, dy);
	}
      }
      p1.setLocation((float) p1.getX() + dx, (float) p1.getY() + dy);
      p2.setLocation((float) p2.getX() + dx, (float) p2.getY() + dy);
      calculateBounds();
      if (parent.isFitted()) {
	parent.componentMoved(this, dx, dy);
      }
    }
  }

  public void componentDimensionChanged(DrawShape comp) {
    if (comp != null) { 
      Rectangle2D b = comp.getBounds();
      float x1 = (float) b.getX();
      float y1 = (float) b.getY();
      float x2 = x1 + (float) b.getWidth();
      float y2 = y1 + (float) b.getHeight();
      float xmin = (float) (Math.min(p1.getX(), p2.getX()));
      float xmax = (float) (Math.max(p1.getX(), p2.getX()));
      float ymin = (float) (Math.min(p1.getY(), p2.getY()));
      float ymax = (float) (Math.max(p1.getY(), p2.getY()));
      boolean needAdjust = (((xmin + cpady) < x1) || 
			    ((ymin + cpady) < y1) ||
			    ((xmax - cpady) > x2) || 
			    ((ymax - cpady) > y1));
      if (needAdjust) { 
	layoutComponents();
	adjustParentLayout();
      }      
    }
  }  

  public void adjustParentLayout() {
    if (debug) {
      System.out.println("DrawShapeBase.adjustParentLayout()");
    }

    DrawShape parent = getParent();
    GraphNodeBase node = null;
    while (parent != null) { 
      parent.layoutComponents();
      parent = parent.getParent();
    }
  }

  public void positionShape(DrawShape shape, Rectangle2D rect) {
    if (shape != null && rect != null) {
      float x = (float) rect.getX();
      float y = (float) rect.getY();
      float w = (float) rect.getWidth();
      float h = (float) rect.getHeight();
      positionShape(shape, x, y, w, h, 0.0f, 0.0f);
    }
  }

  public void positionShape(DrawShape shape, Rectangle2D rect, Offset offset) {
    if (shape != null && rect != null) {
      float x = (float) rect.getX();
      float y = (float) rect.getY();
      float w = (float) rect.getWidth();
      float h = (float) rect.getHeight();
      float dx = 0f;
      float dy = 0f;
      if (offset != null) {
	dx = offset.dx;
	dy = offset.dy;
      }
      positionShape(shape, x, y, w, h, dx, dy);
    }
  }

  public void positionShape(DrawShape shape, float x, float y, float w, float h) {
    if (shape != null) {
      positionShape(shape, x, y, w, h, 0.0f, 0.0f);
    }
  }

  public void positionShape(DrawShape shape, float x, float y, float w, float h, 
			    Offset offset) {
    if (shape != null) {
      float dx = 0f;
      float dy = 0f;
      if (offset != null) {
	dx = offset.dx;
	dy = offset.dy;
      }
      positionShape(shape, x, y, w, h, dx, dy);
    }
  }

  /**
   * Position shape at the center of rect [x, y, w, h], when dx == 0 and
   * dy == 0 (dx, dy) offset to the center position, proportional to the w
   * and h dx, dy in range [-1.0, 1.0]
   */
  public void positionShape(DrawShape shape, float x, float y, float w, float h, 
			    float dx, float dy) {
    if (shape != null) {
      Rectangle2D b = shape.getBounds();
      float sw = (float) b.getWidth();
      float sh = (float) b.getHeight();
      if (dx < -1) {
	dx = -1;
      }
      if (dx > 1) {
	dx = 1;
      }
      if (dy < -1) {
	dy = -1;
      }
      if (dy > 1) {
	dy = 1;
      }
      float px = x + (w - sw) / 2 + dx * w / 2;
      float py = y + (h - sh) / 2 + dy * h / 2;
      shape.setPosition(px, py);
    }
  }

  public void positionShape(DrawShape shape, float x, float y, float w, float h, 
			    int valign, int halign) {
    positionShape(shape, x, y, w, h, valign, halign, 0, 0, true);
  }

  public void positionShape(DrawShape shape, float x, float y, float w, float h, 
			    int valign, int halign, 
			    float vpad, float hpad, boolean inside) {
    if (shape != null) {
      Rectangle2D b = shape.getBounds();
      float sw = (float) b.getWidth();
      float sh = (float) b.getHeight();
      float hgap = (w - sw);
      float vgap = (h - sh);
      float dx = 0.0f;
      float dy = 0.0f;
      if (inside) {
	switch (valign) {
	case SwingConstants.TOP:
	  dy = vpad;
	  break;
	case SwingConstants.BOTTOM:
	  dy = vgap - vpad;
	  break;
	case SwingConstants.CENTER:
	default:
	  dy = vgap / 2;
	  break;
	}
	switch (halign) {
	case SwingConstants.LEFT:
	  dx = hpad;
	  break;
	case SwingConstants.RIGHT:
	  dx = hgap - hpad;
	  break;
	case SwingConstants.CENTER:
	default:
	  dx = hgap / 2;
	  break;
	}
      } else {
	// outside
	switch (valign) {
	case SwingConstants.TOP:
	  dy = -sh - vpad;
	  break;
	case SwingConstants.BOTTOM:
	  dy = h + vpad;
	  break;
	case SwingConstants.CENTER:
	default:
	  dy = vgap / 2;
	  break;
	}
	switch (halign) {
	case SwingConstants.LEFT:
	  dx = -sw - hpad;
	  break;
	case SwingConstants.RIGHT:
	  dx = w + hpad;
	  break;
	case SwingConstants.CENTER:
	default:
	  dx = hgap / 2;
	  break;
	}
      }
      shape.setPosition(x + dx, y + dy);
    }
  }

  protected String getXMLElementName() {
    String name = getClass().getName();
    int i = name.lastIndexOf('.');
    if (i >= 0) {
      return name.substring(i + 1);
    } else {
      return name;
    }
  }

  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = new HashMap<String, Object>();
    attrs.put("filled", filled);
    attrs.put("shadow", shadow);
    attrs.put("shadowWidth", shadowWidth);
    attrs.put("blured", blured);
    attrs.put("showBorder", showborder);
    return attrs;
  }

  protected void writeXMLElements(CodeWriter out, int mode,
				  String namespacePrefix) {
  }

  public void writeXML(CodeWriter out, int mode, String namespacePrefix) {
    if (out != null) {
      String nodename = getPrefixedName(getXMLElementName(), namespacePrefix);
      out.indentInc();
      out.print("<" + nodename);
      Map<String, Object> attrMap = getXMLAttributes();
      if (attrMap != null && attrMap.size() > 0) {
	StringBuilder sb = new StringBuilder();
	Iterator<Map.Entry<String, Object>> iter = attrMap.entrySet().iterator();
	while (iter.hasNext()) {
	  Map.Entry<String, Object> e = iter.next();
	  String attr = getXMLAttributeString(e.getKey(), e.getValue());
	  sb.append(' ').append(attr);
	}
	out.print(sb.toString());
      }
      out.println(">");

      writeXMLBase(out, mode, namespacePrefix, !(this instanceof CompoundDrawShape));
      writeXMLElements(out, mode, namespacePrefix);

      out.thisIndentDec();
      out.println("</" + nodename + ">");
    }
  }

  /*
   * public void writeXML(CodeWriter out, String namespacePrefix) {
   * writeXML(out, WRITE_XML_DEFAULT, namespacePrefix); }
   * 
   * public void writeXML(CodeWriter out) { writeXML(out, null); }
   */

  public static DrawShapeBase makeShapeFromXML(org.w3c.dom.Element node) {
    return makeShapeFromXML(node, null);
  }

  public static DrawShapeBase makeShapeFromXML(org.w3c.dom.Element node,
					       String namespaceURI) {
    if (node != null && xmlBuilder != null) {
      return xmlBuilder.buildShape(node, namespaceURI);
    }
    return null;
  }

  public static String getPrefixedName(String name, String namespacePrefix) {
    if (namespacePrefix != null && namespacePrefix.length() > 0) {
      return namespacePrefix + ":" + name;
    } else {
      return name;
    }
  }

  protected void writeXMLBase(CodeWriter out) {
    writeXMLBase(out, WRITE_XML_DEFAULT, null, true);
  }

  protected void writeXMLBase(CodeWriter out, boolean writeSubshapes) {
    writeXMLBase(out, WRITE_XML_DEFAULT, null, writeSubshapes);
  }

  protected void writeXMLBase(CodeWriter out, String namespacePrefix) {
    writeXMLBase(out, WRITE_XML_DEFAULT, namespacePrefix);
  }

  protected void writeXMLBase(CodeWriter out, int mode, String namespacePrefix) {
    writeXMLBase(out, mode, namespacePrefix, true);
  }

  protected void writeXMLBase(CodeWriter out, int mode, String namespacePrefix,
			      boolean writeSubshapes) {
    if (out != null) {
      String nodename, attrstr;
      if (name != null) {
	nodename = getPrefixedName("Name", namespacePrefix);
	out.println("<" + nodename + ">" + name + "</" + nodename + ">");
      }
      if (templateName != null) {
	nodename = getPrefixedName("Template", namespacePrefix);
	out.println("<" + nodename + ">" + templateName + "</" + nodename + ">");
      }
      if (displayName != null) {
	nodename = getPrefixedName("Display", namespacePrefix);
	out.println("<" + nodename + ">" + displayName + "</" + nodename + ">");
      }
      if (role >= 0) {
	nodename = getPrefixedName("Role", namespacePrefix);
	attrstr = getXMLAttributeString("role", role);
	if (roleName != null) {
	  out.println("<" + nodename + ' ' + attrstr + ">" + roleName + "</" + nodename + ">");
	} else {
	  out.println("<" + nodename + ' ' + attrstr + "/>");
	}
      }
      if (annotation != null) {
	nodename = getPrefixedName("Annotation", namespacePrefix);
	out.println("<" + nodename + ">" + annotation + "</" + nodename + ">");
      }
      /*
       * if (bounds != null) { nodename = getPrefixedName("Bound",
       * namespacePrefix); out.println("<" + nodename + " x=\"" +
       * bounds.getX() + "\" y=\"" + bounds.getY() + "\" w=\"" +
       * bounds.getWidth() + "\" h=\"" + bounds.getHeight() + "\"/>"); }
       */
      if (!(this instanceof PolyShape)) {
	if (p1 != null && p2 != null) {
	  nodename = getPrefixedName("Ends", namespacePrefix);
	  out.println("<" + nodename + ' ' + 
		      getXMLAttributeString("x1", p1.getX()) + ' ' + 
		      getXMLAttributeString("y1", p1.getY()) + ' ' + 
		      getXMLAttributeString("x2", p2.getX()) + ' ' + 
		      getXMLAttributeString("y2", p2.getY()) + "/>");
	}
      }
      if (angle != 0.0f) {
	nodename = getPrefixedName("Transform", namespacePrefix);
	out.println("<" + nodename + ' ' + getXMLAttributeString("a", angle) + "/>");
      }
      if (attr != null) {
	attr.writeXML(out, namespacePrefix);
      }
      if (head != null) {
	nodename = getPrefixedName("Head", namespacePrefix);
	out.indentInc();
	out.println("<" + nodename + ">");
	head.writeXML(out, namespacePrefix);
	out.thisIndentDec();
	out.println("</" + nodename + ">");
      }
      if (tail != null) {
	nodename = getPrefixedName("Tail", namespacePrefix);
	out.indentInc();
	out.println("<" + nodename + ">");
	tail.writeXML(out, namespacePrefix);
	out.thisIndentDec();
	out.println("</" + nodename + ">");
      }

      nodename = getPrefixedName("Attributes", namespacePrefix);
      out.print("<" + nodename + ' ' + 
		((!visible) ? (getXMLAttributeString("visible", visible) + ' ') : "") + 
		((!editable) ? (getXMLAttributeString("editable", editable) + ' ') : "") + 
		((sticky) ? (getXMLAttributeString("sticky", sticky) + ' ') : "") + 
		((fitted) ? (getXMLAttributeString("fitted", fitted) + ' ') : "") + 
		((acceptSubshape) ? (getXMLAttributeString("acceptSubshape", acceptSubshape) + ' ') : "") + 
		((clipSubshapes) ? (getXMLAttributeString("clipSubshapes", clipSubshapes) + ' ') : "") + 
		((!atomic) ? (getXMLAttributeString("atomic", atomic) + ' ') : "") + 
		((!movable) ? (getXMLAttributeString("movable", movable) + ' ') : "") + 
		((!scalable) ? (getXMLAttributeString("scalable", scalable) + ' ') : "") + 
		((!searchable) ? (getXMLAttributeString("searchable", searchable) + ' ') : "") + 
		((!selectable) ? (getXMLAttributeString("selectable", selectable) + ' ') : "") + 
		((!resizable) ? (getXMLAttributeString("resizable", resizable) + ' ') : "") + 
		((!detachable) ? (getXMLAttributeString("detachable", detachable) + ' ') : "") + 
		((!allowRotation) ? (getXMLAttributeString("rotation", allowRotation) + ' ') : "") +
		((!symetricResize) ? (getXMLAttributeString("symetric", symetricResize) + ' ') : "") + 
		((deco != -1) ? (getXMLAttributeString("deco", deco) + ' ') : "") + 
		((flag != -1) ? (getXMLAttributeString("flag", flag) + ' ') : "") + 
		getXMLAttributeString("scale", scaleMode));
      if (customAttr != null) { 
	out.println(">");
	out.thisIndentInc();
	for (Map.Entry<String,Object> e : customAttr.entrySet()) { 
	  String atag = getPrefixedName("Attribute", namespacePrefix);
	  out.println("<" + atag + " key=\"" + e.getKey() + 
		      "\" value=\"" + e.getValue() + "\"/>");
	}
	out.thisIndentDec();
	out.println("</" + nodename + ">");
      } else { 
	out.println("/>");
      }

      // writeXMLModelAttributes(out, mode, namespacePrefix);
      writeXMLModelRef(out, mode, namespacePrefix);

      if (writeSubshapes) {
	writeXMLSubShapes(out, mode, namespacePrefix);
      }
    }
  }

  public void setAttrFromXML(org.w3c.dom.Element node) {
    setAttrFromXML(node, null);
  }

  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      String s;
      s = node.getAttribute("filled");
      filled = Boolean.valueOf(s);
      s = node.getAttribute("shadow");
      shadow = Boolean.valueOf(s);
      s = node.getAttribute("blured");
      blured = Boolean.valueOf(s);
      s = node.getAttribute("showBorder");
      if (s != null && s.length() > 0) {
	showborder = Boolean.valueOf(s);
      } else {
	showborder = true;
      }
    }
  }

  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
  }

  public void setAttrFromXML(org.w3c.dom.Element node, String namespaceURI) {
    if (node != null) {
      processXMLAttributes(node);
      setBaseAttrFromXML(node, namespaceURI);
      doLayout();
      calculateBounds();
    }
  }

  protected void setBaseAttrFromXML(org.w3c.dom.Element node,
				    String namespaceURI) {
    if (node != null) {
      org.w3c.dom.NodeList nodes = node.getChildNodes();
      if (nodes != null) {
	int n = nodes.getLength();
	for (int i = 0; i < n; i++) {
	  org.w3c.dom.Node cnode = nodes.item(i);
	  if (cnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
	    org.w3c.dom.Element e = (org.w3c.dom.Element) cnode;
	    String name = e.getLocalName();
	    String uri = e.getNamespaceURI();
	    if (namespaceURI == null || namespaceURI.equals(uri)) {
	      if ("Name".equals(name)) {
		name = XMLUtil.getTextChildNodeValue(e);
	      } else if ("Display".equals(name)) {
		displayName = XMLUtil.getTextChildNodeValue(e);
		if (displayName == null) { 
		  displayName = "";
		}
	      } else if ("Role".equals(name)) {
		String roleStr = e.getAttribute("role");
		try {
		  int r = Integer.parseInt(roleStr);
		  if (r >= 0) {
		    role = r;
		  }
		} catch (NumberFormatException ex) {
		}
		roleName = XMLUtil.getTextChildNodeValue(e);

		/*
                 * roleStr = XMLUtil.getTextChildNodeValue(e); if (roleStr !=
                 * null && roleStr.length() > 0) { roleName = roleStr; }
                 */
	      } else if ("Annotation".equals(name)) {
		annotation = XMLUtil.getTextChildNodeValue(e);
	      } else if ("Ends".equals(name)) {
		if (!(this instanceof PolyShape)) {
		  if (debug) {
		    System.out.println("DrawShapeBase.setAttrFromXML(): <Ends> tag before: " + this);
		  }

		  String xs1, ys1, xs2, ys2;
		  float x1, y1, x2, y2;
		  xs1 = e.getAttribute("x1");
		  ys1 = e.getAttribute("y1");
		  xs2 = e.getAttribute("x2");
		  ys2 = e.getAttribute("y2");
		  try {
		    x1 = Float.parseFloat(xs1);
		    y1 = Float.parseFloat(ys1);
		    x2 = Float.parseFloat(xs2);
		    y2 = Float.parseFloat(ys2);
		    // setEnds(x1, y1, x2, y2);
		    // setEndsAdj(x1, y1, x2, y2);
		    setEndsInit(x1, y1, x2, y2);

		    if (debug) {
		      System.out.println("DrawShapeBase.setAttrFromXML(): <Ends> tag x1=" + 
					 x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2);
		    }
		  } catch (NumberFormatException ex) {
		  }

		  if (debug) {
		    System.out.println("DrawShapeBase.setAttrFromXML(): <Ends> tag after: " + this);
		  }
		}

	      } else if ("Bound".equals(name)) {
		/*
                 if (debug) {
                 System.out.println("DrawShapeBase.setAttrFromXML(): <Bound> tag before: " + this); }
                 String xs, ys, ws, hs; float x, y, w, h; 
		 xs = e.getAttribute("x"); ys = e.getAttribute("y"); 
		 ws = e.getAttribute("w"); hs = e.getAttribute("h"); 
		 try { x = Float.parseFloat(xs); y = Float.parseFloat(ys);
		 w = Float.parseFloat(ws); h = Float.parseFloat(hs);
                 bounds.setRect(x, y, w, h); } catch (NumberFormatException ex) {}
		*/
	      } else if ("Transform".equals(name)) {
		String as;
		float a;
		as = e.getAttribute("a");
		try {
		  a = Float.parseFloat(as);
		  if (a != 0.0f) {
		    setRotationAngle(a);
		  }
		} catch (NumberFormatException ex) {
		}
	      } else if ("Color".equals(name) || "FillColor".equals(name) || 
			 "GradientColor1".equals(name) || "GradientColor2".equals(name) ||
			 "Gradient".equals(name) || 
			 "Line".equals(name) || "Font".equals(name)) {
		if (attr == null) {
		  attr = new DrawAttr();
		}
		attr.setAttrFromXML(e);
	      } else if ("Head".equals(name)) {
		if (debug) {
		  System.out.println("DrawShapeBase.setAttrFromXML(): <Head> tag");
		}
		org.w3c.dom.NodeList hcnodes = e.getChildNodes();
		if (hcnodes != null) {
		  int hcn = hcnodes.getLength();
		  for (int j = 0; j < hcn; j++) {
		    org.w3c.dom.Node hcnode = hcnodes.item(j);
		    uri = hcnode.getNamespaceURI();
		    if (hcnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
			(namespaceURI == null || namespaceURI.equals(uri)) && 
			"Arrow".equals(hcnode.getLocalName())) {
		      head = new Arrow();
		      head.setOwner(this);
		      head.setAttrFromXML((org.w3c.dom.Element) hcnode);
		    }
		  }
		}
	      } else if ("Tail".equals(name)) {
		if (debug) {
		  System.out.println("DrawShapeBase.setAttrFromXML(): <Tail> tag");
		}
		org.w3c.dom.NodeList tcnodes = e.getChildNodes();
		if (tcnodes != null) {
		  int tcn = tcnodes.getLength();
		  for (int j = 0; j < tcn; j++) {
		    org.w3c.dom.Node tcnode = tcnodes.item(j);
		    uri = tcnode.getNamespaceURI();
		    if (tcnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
			(namespaceURI == null || namespaceURI.equals(uri)) && 
			"Arrow".equals(tcnode.getLocalName())) {
		      tail = new Arrow();
		      tail.setOwner(this);
		      tail.setAttrFromXML((org.w3c.dom.Element) tcnode);
		    }
		  }
		}
	      } else if ("Attributes".equals(name)) {
		String s;
		s = e.getAttribute("visible");
		if (s != null && s.length() > 0) {
		  visible = Boolean.valueOf(s);
		}
		s = e.getAttribute("movable");
		if (s != null && s.length() > 0) {
		  movable = Boolean.valueOf(s);
		}
		s = e.getAttribute("atomic");
		if (s != null && s.length() > 0) {
		  atomic = Boolean.valueOf(s);
		}
		s = e.getAttribute("sticky");
		if (s != null && s.length() > 0) {
		  sticky = Boolean.valueOf(s);
		}
		s = e.getAttribute("fitted");
		if (s != null && s.length() > 0) {
		  fitted = Boolean.valueOf(s);
		}
		s = e.getAttribute("acceptSubshape");
		if (s != null && s.length() > 0) {
		  acceptSubshape = Boolean.valueOf(s);
		}
		s = e.getAttribute("clipSubshapes");
		if (s != null && s.length() > 0) {
		  clipSubshapes = Boolean.valueOf(s);
		}
		s = e.getAttribute("editable");
		if (s != null && s.length() > 0) {
		  editable = Boolean.valueOf(s);
		}
		s = e.getAttribute("rotation");
		if (s != null && s.length() > 0) {
		  allowRotation = Boolean.valueOf(s);
		}
		s = e.getAttribute("symetric");
		if (s != null && s.length() > 0) {
		  symetricResize = Boolean.valueOf(s);
		}
		s = e.getAttribute("scalable");
		if (s != null && s.length() > 0) {
		  scalable = Boolean.valueOf(s);
		}
		s = e.getAttribute("searchable");
		if (s != null && s.length() > 0) {
		  searchable = Boolean.valueOf(s);
		}
		s = e.getAttribute("selectable");
		if (s != null && s.length() > 0) {
		  selectable = Boolean.valueOf(s);
		}
		s = e.getAttribute("resizable");
		if (s != null && s.length() > 0) {
		  resizable = Boolean.valueOf(s);
		}
		s = e.getAttribute("detachable");
		if (s != null && s.length() > 0) {
		  detachable = Boolean.valueOf(s);
		}

		s = e.getAttribute("deco");
		if (s != null && s.length() > 0) {
		  try {
		    deco = Integer.parseInt(s);
		  } catch (NumberFormatException ex) {
		  }
		}
		s = e.getAttribute("flag");
		if (s != null && s.length() > 0) {
		  try {
		    flag = Integer.parseInt(s);
		  } catch (NumberFormatException ex) {
		  }
		}
		s = e.getAttribute("scale");
		try {
		  scaleMode = Integer.parseInt(s);
		} catch (NumberFormatException ex) {
		}

		if (e.hasChildNodes()) {
		  org.w3c.dom.NodeList attrs = e.getChildNodes();
		  if (attrs != null) {
		    int alen = attrs.getLength();
		    if (alen > 0) { 
		      customAttr = new HashMap<String, Object>();
		      for (int k = 0; k < alen; k++) {
			org.w3c.dom.Node anode = attrs.item(k);
			if (anode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
			  org.w3c.dom.Element attre = (org.w3c.dom.Element) anode;
			  String aname = attre.getLocalName();
			  if ("Attribute".equals(aname)) {
			    String key = attre.getAttribute("key");
			    String value = attre.getAttribute("value");
			    customAttr.put(key, value);
			  }
			}
		      }
		    }
		  }
		}
	      } else if ("Model".equals(name)) {
		String refid = e.getAttribute("refid");
		if (refid != null && refid.length() > 0) {
		  // System.out.println("DrawShapeBase.setBaseAttrFromXML() <Model> refid=" + refid);
		  ModelRepository rep = ModelRepository.getInstance();
		  ModelElement m = rep.getElement(refid);
		  // System.out.println("DrawShapeBase.setBaseAttrFromXML() <Model> model=" + m);
		  setModel(m);
		} else {
		  if (model != null) {
		    org.w3c.dom.NodeList snodes = e.getChildNodes();
		    if (snodes != null) {
		      int sn = snodes.getLength();
		      for (int j = 0; j < sn; j++) {
			org.w3c.dom.Node snode = snodes.item(j);
			uri = snode.getNamespaceURI();
			if (snode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
			    (namespaceURI == null || namespaceURI.equals(uri))) {
			  org.w3c.dom.Element e1 = (org.w3c.dom.Element) snode;
			  String ename = e1.getLocalName();
			  if ("Stereotype".equals(ename)) {
			    String st = e1.getAttribute("name");
			    model.addStereotype(st);
			  } else if ("Tag".equals(ename)) {
			    String tname = e1.getAttribute("name");
			    String tval = e1.getAttribute("value");
			    model.setTagValue(tname, tval);
			    /*
			     * } else if ("Constraint".equals(ename)) { if
			     * (model instanceof Constraint) { Constraint c =
			     * (Constraint) model; String v =
			     * XMLUtil.getTextChildNodeValue(e1);
			     * c.setConstraint(v); } } else if
			     * ("Comment".equals(ename)) { if (model
			     * instanceof Comment) { Comment c = (Comment)
			     * model; String v =
			     * XMLUtil.getTextChildNodeValue(e1);
			     * c.setComment(v); }
			     */
			  }
			}
		      }
		    }
		  }
		}
	      } else if ("SubShapes".equals(name)) {

		if (templateName == null || !atomic) {

		  if (debug) {
		    System.out.println("DrawShapeBase.setBaseAttrFromXML() @1 shape= " + this);
		  }

		  org.w3c.dom.NodeList snodes = e.getChildNodes();
		  if (snodes != null) {
		    int sn = snodes.getLength();
		    for (int j = 0; j < sn; j++) {
		      org.w3c.dom.Node snode = snodes.item(j);
		      uri = snode.getNamespaceURI();
		      if (snode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
			  (namespaceURI == null || namespaceURI.equals(uri))) {
			DrawShape shape = 
			  DrawShapeBase.makeShapeFromXML((org.w3c.dom.Element) snode, namespaceURI);

			if (debug) {
			  System.out.println("DrawShapeBase.setBaseAttrFromXML() @2 subshape= " + 
					     shape);
			}

			if (shape != null) {
			  if (shape.getRole() >= 0 && 
			      shape instanceof Label && 
			      this instanceof GraphComponentShape) {
			    Label label = (Label) shape;
			    GraphComponentShape gcomp = (GraphComponentShape) this;
			    gcomp.setLabel(shape.getRole(), label);
			  } else {
			    addShape(shape);
			  }
			}

			if (debug) {
			  System.out.println("DrawShapeBase.setBaseAttrFromXML() @3 subshape= " + shape);
			}
		      }
		    }
		    arrangeSubshapes();
		    // doLayout();
		  }
		}
		// end handle subshapes

	      } else {
		processXMLElement(e, namespaceURI);
	      }
	    }
	  }
	}
      }
    }
  }

  public void setEndsInit(float x1, float y1, float x2, float y2) {
    setEndsAdj(x1, y1, x2, y2);
  }

  public void writeXMLModelRef(CodeWriter out, int mode, String namespacePrefix) {
    if (out != null && model != null) {
      String nodename = getPrefixedName("Model", namespacePrefix);
      out.println("<" + nodename + ' '
		  + getXMLAttributeString("refid", model.getId()) + "/>");
    }
  }

  public void writeXMLModelAttributes(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null && model != null) {
      boolean hasModel = false;

      Set<String> stereotypes = model.getStereotypes();
      Map<String, String> tags = model.getTags();
      if (stereotypes != null && !stereotypes.isEmpty()) {
	hasModel = true;
      }
      if (tags != null && !tags.isEmpty()) {
	hasModel = true;
      }

      /*
       * String constraint = null; if (model instanceof Constraint) {
       * Constraint c = (Constraint) model; constraint = c.getConstraint(); }
       * String comment = null; if (model instanceof Comment) { Comment c =
       * (Comment) model; comment = c.getComment(); }
       */
      /*
       * if (constraint != null) { hasModel = true; } if (comment != null) {
       * hasModel = true; }
       */
      if (hasModel) {
	String nodename = getPrefixedName("Model", namespacePrefix);
	out.indentInc();
	out.println("<" + nodename + ">");

	// stereotypes
	if (stereotypes != null && !stereotypes.isEmpty()) {
	  String stname = getPrefixedName("Stereotype", namespacePrefix);
	  Iterator<String> siter = stereotypes.iterator();
	  while (siter.hasNext()) {
	    String st = siter.next();
	    out.println("<" + stname + ' ' + getXMLAttributeString("name", st) + "/>");
	  }
	}

	// tags
	if (tags != null && !tags.isEmpty()) {
	  String tname = getPrefixedName("Tag", namespacePrefix);
	  Iterator<Map.Entry<String, String>> titer = tags.entrySet()
	      .iterator();
	  while (titer.hasNext()) {
	    Map.Entry<String, String> tag = titer.next();
	    out.println("<" + tname + ' ' + 
			getXMLAttributeString("name", tag.getKey()) + ' '+ 
			getXMLAttributeString("value", tag.getValue()) + "/>");
	  }
	}

	/*
         * if (constraint != null) { String cname =
         * getPrefixedName("Constraint", namespacePrefix); out.print("<" +
         * cname + ">"); out.print("<![CDATA[" + constraint + "]]>");
         * out.println("</" + cname + ">"); }
         * 
         * if (comment != null) { String cname = getPrefixedName("Comment",
         * namespacePrefix); out.print("<" + cname + ">"); out.print("<![CDATA[" +
         * comment + "]]>"); out.println("</" + cname + ">"); }
         * 
         */
	out.thisIndentDec();
	out.println("</" + nodename + ">");
      }
    }
  }

  public void writeXMLSubShapes(CodeWriter out, int mode, String namespacePrefix) {
    if (out != null) {
      if (subshapes != null) {
	String nodename = getPrefixedName("SubShapes", namespacePrefix);
	int n = subshapes.size();
	if (n > 0) {
	  out.indentInc();
	  out.println("<" + nodename + ">");
	  for (int i = 0; i < n; i++) {
	    DrawShape shape = (DrawShape) subshapes.get(i);
	    if (shape != null) {
	      shape.writeXML(out, mode, namespacePrefix);
	    }
	  }
	  out.thisIndentDec();
	  out.println("</" + nodename + ">");
	} else {
	  out.println("<" + nodename + "/>");
	}
      }
    }
  }

  public void addSubShapesFromXML(org.w3c.dom.Element node, String namespaceURI) {
    if (node != null) {
      //System.out.println("DrawShapeBase.addSubShapesFromXML()"); 

      org.w3c.dom.NodeList nodes = node.getChildNodes();
      if (nodes != null) {
	int n = nodes.getLength();
	for (int i = 0; i < n; i++) {
	  org.w3c.dom.Node cnode = nodes.item(i);
	  if (cnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
	      "SubShapes".equals(cnode.getLocalName())) {
	    org.w3c.dom.NodeList snodes = cnode.getChildNodes();
	    if (snodes != null) {
	      int sn = snodes.getLength();
	      for (int j = 0; j < sn; j++) {
		org.w3c.dom.Node snode = snodes.item(j);
		String uri = snode.getNamespaceURI();
		if (snode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
		    (namespaceURI == null || namespaceURI.equals(uri))) {
		  DrawShape shape = 
		    DrawShapeBase.makeShapeFromXML((org.w3c.dom.Element) snode, namespaceURI);
								   
		  if (shape != null) {
		    addShape(shape);
		  }
		}
	      }
	    }
	  }
	}
	// fitComponents();
	arrangeSubshapes();
	doLayout();
      }
    }
  }

  protected void arrangeSubshapes() {
  }

  protected void writeXMLFont(CodeWriter out) {
    writeXMLFont(out, null);
  }

  protected void writeXMLFont(CodeWriter out, String namespacePrefix) {
    if (out != null && attr != null) {
      attr.writeXMLFont(out, namespacePrefix);
    }
  }

  protected static void writeXMLPoints(CodeWriter out, List<Point2D> points) {
    writeXMLPoints(out, points, null);
  }

  protected static void writeXMLPoints(CodeWriter out, List<Point2D> points,
				       String namespacePrefix) {
    if (out != null && points != null) {
      String name = getPrefixedName("Points", namespacePrefix);
      out.indentInc();
      out.println("<" + name + ">");
      int count = points.size();
      String pname = getPrefixedName("Point", namespacePrefix);
      for (int i = 0; i < count; i++) {
	Point2D p = points.get(i);
	out.println("<" + pname + ' ' + 
		    getXMLAttributeString("x", p.getX()) + ' ' + 
		    getXMLAttributeString("y", p.getY()) + "/>");
      }
      out.thisIndentDec();
      out.println("</" + name + ">");
    }
  }

  protected static DrawShapeXMLBuilder xmlBuilder = new DrawShapeXMLBuilder();

  protected DrawShapeBase() {
    this(false);
  }

  protected DrawShapeBase(boolean hasSubShapes) {
    bounds = new Rectangle2DFloat();
    framebounds = new Rectangle2DFloat();
    p1 = new Point2DFloat();
    p2 = new Point2DFloat();
    if (hasSubShapes) {
      subshapes = new ArrayList<DrawShape>();
    }
  }

  protected static int getDrawOutlineMode() {
    return drawOutlineMode;
  }

  protected static void setDrawOutlineMode(int drawOutlineMode) {
    DrawShapeBase.drawOutlineMode = drawOutlineMode;
  }

  protected static final boolean debug = false;

  protected static boolean debug_drawframe = false;

  public static void setDebugDrawFrame(boolean f) { 
    debug_drawframe = f;
  }

}
